/*
 * mapcache.c - functions deal with the rssi map structure operations
 */

#include "bpcommon.h"


/** 
  * rssibind_init - create and initialize the rssi_bind element
  * returns: the pointer to newly created element - on success; NULL on failure
  */
struct rssi_bind* rssibind_init(void)
{
	int i;
	struct rssi_bind *newbind;

	newbind = (struct rssi_bind *)malloc(sizeof(struct rssi_bind));
	if (newbind == NULL)
		return NULL;

	/* MAC address is all zero */
	memset(newbind->macaddr, 0, MAC_ADDR_LEN);
	/* HASH address is all 0xFF, thus always largest during sorting */
	memset(newbind->hashaddr, 255, HASH_ADDR_LEN);
	/* values in rssi sample list are initialized to 100 dBm */
	for (i = 0; i < WIFI_SAMPLE_COUNT; i++){
		newbind->sample_db[i] = INVALID_RSSI;
	}
	/* stats are all zero */
	newbind->mean_db = newbind->stddev_db = newbind->sample_cnt = 0;
	/* next pointer is NULL */
	newbind->next = NULL;

	return newbind;
}

/** 
  * rssibind_free - destroy rssi_bind list of a fingerprint specified in the parameter
  *                     it is NOT THE SAME as the initial state of fingerprint since even the tail element is destroyed
  * @struct rssi_fgpt *rssifgpt - a pointer to a rssi fingerprint element
  * returns: 0 on success; -1 on failure
  */
static int rssibind_free(struct rssi_fgpt *rssifgpt)
{
	struct rssi_bind *current;
	struct rssi_bind *next;

	current = rssifgpt->bind_head;
	while (current){
		next = current->next;
		free(current);
		current = next;
	}
	rssifgpt->bind_head = NULL;
	
	return 0;
}

/** 
  * rssibind_print - enumerate wifi scan detail for each rssi_bind at each round
  * 				not useful, SHOULD use rssifgpt_print instead 
  * @struct rssi_fgpt *rssifgpt - a pointer to a rssi fingerprint element
  * @FILE *debug - file descriptor of output
  * returns: 0 on success; -1 on failure
  */
int rssibind_print(struct rssi_fgpt *rssifgpt, FILE *debug)
{
	struct rssi_bind *printbind = rssifgpt->bind_head;
	int round;

	for (round = 0; round < WIFI_SAMPLE_COUNT; round++){
		fprintf(debug, "Round: %d\n", round);
		while (printbind){
			fprintf(debug, "%.2X:%.2X:%.2X:%.2X:%.2X\t%d\n", 
							printbind->hashaddr[0], printbind->hashaddr[1],
	                        printbind->hashaddr[2], printbind->hashaddr[3], 
	                        printbind->hashaddr[4], printbind->sample_db[round]);
			printbind = printbind->next;
		}
	}
}

/** 
  * rssifgpt_init - create and initialize the RSSI fingerprint structure
  * 				the resulting fingerprint has a bind list with a single sentinel element in it
  * returns: the pointer to newly created element - on success; NULL on failure
  */
static struct rssi_fgpt* rssifgpt_init(void)
{
	struct rssi_fgpt *newfgpt;

	newfgpt = (struct rssi_fgpt *)malloc(sizeof(struct rssi_fgpt));
	if (newfgpt == NULL)
		return NULL;

	/* initial location is always unknown*/
	strcpy(newfgpt->loc, "unknown");
	/* the tail element with largest hash address */
	newfgpt->bind_cnt = 1;
	/* initially fingerprints are all just reported until they are confirmed by human owner */
	newfgpt->commited = 0;
	/* construct an AP list with single tail element and return it as head*/ 
	newfgpt->bind_head = rssibind_init();
	if (newfgpt->bind_head == NULL)
		return NULL;
	/* no next element */
	newfgpt->next = NULL;

	return newfgpt;
}

/** 
  * rssifgpt_free - destroy rssi_fgpt list of a map defined in the parameter, 
  *                     including the RSSI scan list of each fingerprint 
  * @struct rssi_map *rssimap - a pointer to a rssi map element
  * returns: 0 on success; -1 on failure
  */
static int rssifgpt_free(struct rssi_map *rssimap)
{
	struct rssi_fgpt *current;
	struct rssi_fgpt *next;

	current = rssimap->fgpt_head;
	while (current){
		next = current->next;
		rssibind_free(current);
		free(current);
		current = next;
	}
	rssimap->fgpt_head = NULL;
	
	return 0;
}

/** 
  * rssifgpt_print - print rssi fingerprint infomation, location and the rssi_bind list
  * @struct rssi_fgpt *rssifgpt - a pointer to a rssi fingerprint element
  * @FILE *debug - file descriptor
  * returns: 0 on success; -1 on failure
  */
int rssifgpt_print(struct rssi_fgpt *rssifgpt, FILE *debug)
{
	struct rssi_bind *printbind = rssifgpt->bind_head;

	fprintf(debug, "Location: %s\tNumber of APs: %d\n", rssifgpt->loc, rssifgpt->bind_cnt - 1);
	while (printbind){
		fprintf(debug, "%.2X:%.2X:%.2X:%.2X:%.2X\t%d\t%2.2f\t%d\n", 
						printbind->hashaddr[0], printbind->hashaddr[1],
                        printbind->hashaddr[2], printbind->hashaddr[3], 
                        printbind->hashaddr[4], printbind->mean_db, 
						printbind->stddev_db, printbind->sample_cnt);
        printbind = printbind->next;
	}

	return 0;
}

/** 
  * rssifgpt_fuse - fuse fingerprints from beacons with local fingerprint, 
  * 		 		to make local fingerprint a superset of all APs we can hear of 
  * @struct rssi_fgpt *local - a pointer to the local fingerprint structure
  * @struct rssi_fgpt *bcon - a pointer to the bcon fingerprint structure
  * returns: 0 on success; 
  */
static int rssifgpt_fuse(struct rssi_fgpt *local, struct rssi_fgpt *bcon)
{
	struct rssi_bind *localbind = local->bind_head;
	struct rssi_bind *bconbind = bcon->bind_head;
	struct rssi_bind *priorbind, *newbind;

	priorbind = newbind = NULL;
	while(bconbind->sample_cnt){
		while(addrcmp(localbind->hashaddr, bconbind->hashaddr) < 0){
			/* rssi address is smaller than beacon address, 
				some AP is sensed by local but not the beacon*/
			/* simply move to next rssi sample */
			priorbind = localbind;
			localbind = localbind->next;
		}
		if (addrcmp(localbind->hashaddr, bconbind->hashaddr) > 0){
			/* rssi address is larger than beacon address, 
			     beacon contains some AP we didn't sensed */
			/* add this missed AP into list */
			newbind = rssibind_init();
			memcpy(newbind->hashaddr, bconbind->hashaddr, HASH_ADDR_LEN);
			/* ignore mac address */
			/* set rssi value to invalid value */
			newbind->mean_db = INVALID_RSSI;
			/* set rssi count to 1 */
			newbind->sample_cnt = 1;
			newbind->next = localbind;
			/* bind count for local fingerprint is incremented */
			local->bind_cnt++;
			/* redetermine the head */
			if (priorbind != NULL){
				priorbind->next = newbind;
			} else {
				/* change of head can only happen once */
				local->bind_head = newbind;
			}
			priorbind = newbind;
		}
		else if(addrcmp(localbind->hashaddr, bconbind->hashaddr) == 0){
			/* rssi address is equal to beacon address */
			/* do nothing */
		}
		bconbind = bconbind->next;
	}

	return 0;
}

/** 
  * rssifgpt_update_wifi - update local rssi fingerprint from wi-fi scan result 
  * @struct rssi_fgpt *local - a pointer to the local fingerprint (first element of map)
  * @struct wireless_scan_head *wsh - pointer to wireless scan result
  * @int round - round number
  * returns: 0 on success;
  */
int rssifgpt_update_wifi(struct rssi_fgpt *local, struct wireless_scan_head *wsh, int round, int scan_num)
{
	struct wireless_scan *ws; 
	struct rssi_bind *pos, *prior, *newbind; 
	int dblevel, dbsum, i;
	u_char ap_hashaddr[HASH_ADDR_LEN];

	if ((round == 0)&&(local->bind_cnt != 1)){
		/* if there is a previous record of fingerprint */
		/* purge the fingerprint */
		rssibind_free(local);
		local->bind_head = rssibind_init();
		local->bind_cnt = 1;
	}

	/* parse wifi scan results */
	ws = wsh->result;
	while (ws != NULL){
		pos = local->bind_head;
		prior = NULL;
		addrhash(ws->ap_addr.sa_data, ap_hashaddr);
		while (addrcmp(pos->hashaddr, ap_hashaddr) < 0){
			/* current address is smaller than AP address, move to next */
			prior = pos; 
			pos = pos->next;
		}
		if (addrcmp(pos->hashaddr, ap_hashaddr) > 0){
			/* current address is larger than AP address, no match found
			add new element before current address */
			newbind = rssibind_init();
			memcpy(newbind->hashaddr, ap_hashaddr, HASH_ADDR_LEN);
			memcpy(newbind->macaddr, ws->ap_addr.sa_data, MAC_ADDR_LEN);
			dblevel = ws->stats.qual.level;
			if (dblevel >= 64) {
				dblevel -= 0x100;
			}
			/* convert to positive */
			dblevel = 0 - dblevel;
			if (dblevel != 0){
				/* normal, counted */
				newbind->sample_db[round] = dblevel;
				newbind->sample_cnt += 1;
			}else{ 
				/* in some strange case rssi is reported 0, ignore such case */
				newbind->sample_db[round] = INVALID_RSSI;
			}
			/* add newbind into list */
			newbind->next = pos;
			/* redetermine the head */
			if (prior != NULL){
				/* prior is not null, head remain */
				prior->next = newbind;
			} else {
				/* prior is null, insert before head */
				local->bind_head = newbind;
			}
		} 
		else if (addrcmp(pos->hashaddr, ap_hashaddr) == 0){
			/* match found, update new measurement */
			dblevel = ws->stats.qual.level;
			if (dblevel >= 64) {
				dblevel -= 0x100;
			}
			/* convert to positive */
			dblevel = 0 - dblevel;
			if (dblevel != 0){
				/* normal, counted */
				pos->sample_db[round] = dblevel;
				pos->sample_cnt += 1;
			}else {
				/* abnormal, ignore */
				pos->sample_db[round] = INVALID_RSSI;
			}
		}
		ws = ws->next;
	}

	/* last round, calculate statistics */
	if (round == (scan_num - 1)){
		pos = local->bind_head; 
		local->bind_cnt = 1;
		while(pos->next){
			/* not tail */
			if (pos->sample_cnt != 0){
				/* calculating mean */
				/* only consider rssi values that is valid */
				dbsum = 0;
				for (i = 0; i < scan_num; i++){
					if (pos->sample_db[i] != INVALID_RSSI)
						dbsum += pos->sample_db[i];
				}
				pos->mean_db = dbsum / pos->sample_cnt;

				/* calculating standard derivation */
				dbsum = 0; 
				for (i = 0; i < scan_num; i++){
					if (pos->sample_db[i] != INVALID_RSSI)
						dbsum += pow((abs(pos->sample_db[i]) - pos->mean_db), 2);
					pos->stddev_db = sqrt((double) dbsum / (double)(pos->sample_cnt));
				}
			}else {
				/* the case we received rssi report but the report is invalid (0 dbm) */
				/* bind is added but value is not taken into account */
				pos->mean_db = INVALID_RSSI;
			}
			/*bind_cnt record the number of all APs, not just the normal one*/
			local->bind_cnt++;
			pos = pos->next;
		}
		return 1;
	}

	return 0;
}

/** 
  * rssifgpt_update_file - update rssi fingerprint element from a log file, one record at a time
  * @struct rssi_fgpt *rssifgpt - a pointer to the fingerprint structure we need to update
  * @FILE *fp - file pointer to input log file
  * @int room - room identifier, for writing location field
  * @int posmk - position identifier, for writing location field and specify appropriate position we should be reading
  * returns: 0 on end of fingerprint; -1 on EOF
  */
ssize_t rssifgpt_update_file(struct rssi_fgpt *rssifgpt, FILE *fp, int room, int posmk)
{
	char *line = NULL;
	size_t len = 0;
	ssize_t read;

	int readposmk; 			/* position we are reading */
	int posflag = 0;		/* position flag, set to one if we are at the correct fingerprint record */
	int bindcnt;
	
	/* target bind, start with the head */
	struct rssi_bind *currentbind = rssifgpt->bind_head;
	
	while((read = getline(&line, &len, fp)) != -1){
		if(sscanf(line, "Location: %d", &readposmk)){
			/* parse location */
			if (readposmk == posmk){
				/* at the right position, set flag and write location*/
				posflag = 1;
				sprintf(rssifgpt->loc, "%d_%d", room, posmk);
			}
		}
		if (posflag){
			if (rssifgpt->bind_cnt != 1){
				/* if there is a previous record of fingerprint */
				/* purge the fingerprint */
				rssibind_free(rssifgpt);
				rssifgpt->bind_head = rssibind_init();
				rssifgpt->bind_cnt = 1;
				currentbind = rssifgpt->bind_head;
			}
			if(sscanf(line, "Number of Scans: %d", &bindcnt)){
				
			}else if(sscanf(line, "%2X:%2X:%2X:%2X:%2X\t%d\t%lf\t%d", 
				&(currentbind->hashaddr[0]), &(currentbind->hashaddr[1]), 
				&(currentbind->hashaddr[2]), &(currentbind->hashaddr[3]), 
				&(currentbind->hashaddr[4]), &(currentbind->mean_db), 
				&(currentbind->stddev_db), &(currentbind->sample_cnt))){
				/* parse scans */
				if (currentbind->sample_cnt){
					/* not the tail bind? continue */
					if (!currentbind->next)
						currentbind->next = rssibind_init();
					currentbind = currentbind->next;
				}else{
					/* hit the tail element FF:FF:FF:FF:FF, parse complete */
					if(line)
						free(line);
					/* if we are tail fingerprint, create a new tail */
					if (!rssifgpt->next)
						rssifgpt->next = rssifgpt_init();
					/* parse scan count, including the tail bind */
					rssifgpt->bind_cnt = bindcnt + 1;
					return 0;
				}
			}
		}
	}
	return read;
}

/** 
  * rssifgpt_update_bcon - parse eir data record and update rssi fingerprint element accordingly
  * @struct rssi_fgpt *rssifgpt - a pointer to the fingerprint structure we need to update
  * @char *eirdata - eirdata record
  * returns: 0 on success
  */
int rssifgpt_update_bcon(struct rssi_fgpt *rssifgpt, char *eirdata)
{
	char *pos;
	struct rssi_bind *currentbind = rssifgpt->bind_head;

	/* Parse location */
	pos = eirdata + 5;
	strncpy(rssifgpt->loc, pos, 10);
	pos += 10;

	while (*pos != '\0'){
		/* Parse AP address */
		memcpy(currentbind->hashaddr, pos, HASH_ADDR_LEN);
		pos += HASH_ADDR_LEN;
		/* Parse RSSI */
		currentbind->mean_db = (int)*pos - 10;
		pos++;
		/* Parse standard deviation */
		currentbind->stddev_db = (int)*pos - 40;
		pos++;
		/* Parse sample count */
		currentbind->sample_cnt = (int)*pos;
		pos++;
		if (!currentbind->next)
			currentbind->next = rssibind_init();
		currentbind = currentbind->next;
		rssifgpt->bind_cnt++;
	}
	
	if (!rssifgpt->next)
		rssifgpt->next = rssifgpt_init();

	return 0;
}

/** 
  * rssifgpt_update_dbrecord - update rssi fingerprint element from fingerprint database connection
  * @struct rssi_fgpt *rssifgpt - a pointer to the fingerprint structure we need to update
  * returns: 0 on success
  */
int rssifgpt_update_dbrecord(struct rssi_fgpt *rssifgpt)
{
}

/** 
  * rssifgpt_flush_bcon - purge all beacon fingerprints from map
  * @struct rssi_fgpt *tailfgpt - a pointer to a tail of cache map, one fingerprint ahead of first beacon
  * returns: 0 on success; -1 on failure
  */
int rssifgpt_flush_bcon(struct rssi_fgpt *tailfgpt)
{
	/* tail of map */
	struct rssi_fgpt *prior = tailfgpt;
	/* head of beacon */
	struct rssi_fgpt *current = prior->next;
	struct rssi_fgpt *next;

	/* leave the sentinel node alone*/
	while(current->next){
		next = current->next;
		rssibind_free(current);
		free(current);
		current = next;
	}
	/* attach sentinel node after fingerprint tail */
	prior->next = current;

	return 0; 
}


/** 
  * rssimap_init - create and initialize the RSSI fingerprint map structure
  *				TWO fingerprint structures are created by default, 
  *				one for local fingerprint and one for sentinel element
  * returns: the pointer to newly created element - on success; NULL on failure
  */
struct rssi_map * rssimap_init(void)
{
	struct rssi_map *newmap;

	newmap = (struct rssi_map *)malloc(sizeof(struct rssi_map));
	if (newmap == NULL)
		return NULL;

	/* two fingerprints are created at the same time*/
	newmap->fgpt_cnt = 2;

	/* create first fingerprint that holds local observation */
	newmap->fgpt_head = rssifgpt_init();
	if (newmap->fgpt_head == NULL)
		return NULL;

	/* first fingerprint in map is local */
	strcpy((newmap->fgpt_head)->loc, myposition);

	/* create a sentinel element */
	newmap->fgpt_head->next = rssifgpt_init();
	if (newmap->fgpt_head->next == NULL)
		return NULL;
	
	/* config */
	newmap->globalconfig = (struct configparam *)malloc(sizeof(struct configparam));
	if (newmap->globalconfig == NULL)
		return NULL;
	
	/* no beacon yet */
	newmap->fgpt_tail = newmap->fgpt_head;
	
	return newmap;
		
}

/** 
  * rssifgpt_free - destroy map structure itself
  *                     including the RSSI scan list of each fingerprint 
  * @struct rssi_map *rssimap - a pointer to a rssi map element
  * returns: 0 on success; -1 on failure
  */
int rssimap_free(struct rssi_map *rssimap)
{
	if(rssifgpt_free(rssimap))
		return -1;
	
	free(rssimap);
	return 0;
}

/** 
  * rssimap_print - print fingerprint map infomation
  * @struct rssi_map *rssimap - a pointer to a rssi fingerprint map
  * @FILE *debug - file descriptor
  * returns: 0 on success; -1 on failure
  */
int rssimap_print(struct rssi_map *rssimap, FILE *debug)
{
	struct rssi_fgpt *printbind = rssimap->fgpt_head;

	while (printbind){
		rssifgpt_print(printbind, debug);
        printbind = printbind->next;
	}

	return 0;
}

/** 
  * rssimap_fuse - iterate all cached/beacon fingerprints and fuse them with local scan fingerprint
  * @struct rssi_map *rssimap - a pointer to a rssi fingerprint map
  * @int fusebcon - flag set if only to fuse beacons, otherwise the whole cache map
  * returns: 0 on success; -1 on failure
  */
int rssimap_fuse(struct rssi_map *rssimap, int fusebcon)
{
	/* local fingerprint */
	struct rssi_fgpt *local = rssimap->fgpt_head;
	/* if fusebcon is set, start with beacon fingerprints. 
	    otherwise fuse all fingerprints including both cache map and beacon fingerprints */
	struct rssi_fgpt *peer = (fusebcon == 1) ? rssimap->fgpt_tail->next : local->next;

	while(peer->bind_cnt > 1){
		rssifgpt_fuse(local, peer);
		peer = peer->next;
	}
}

/** 
  * rssimap_load_cache_file - initialize or append the local cached map from local map files                         
  * @struct rssi_map *rssimap - a pointer to a rssi fingerprint map
  * returns: 0 on success; -1 on failure
  */
static int rssimap_load_cache_file(struct rssi_map *rssimap)
{
	/* load cache map from local files */
	FILE *fpbcon;
	int posidx, rmidx, ret;
	char filename[20];
	/* skip the local fingerprint */
	struct rssi_fgpt *currentfgpt = rssimap->fgpt_head->next;
	struct configparam *config = rssimap->globalconfig;

	if (rssimap->fgpt_tail->next->next){
		/* flush all existing beacon fingerprints */
		rssifgpt_flush_bcon(rssimap->fgpt_tail);
	}

	/* NOTE: move to first null fingerprint element and update it */
	if(currentfgpt->next){
		/* This should always be false */
		/* TODO: true if incremental update */
		currentfgpt = currentfgpt->next;
	}

	/* add all beacon fingerprints */
	for (rmidx = 0; rmidx < config->num_rooms; rmidx++){
		sprintf(filename, "stat_%d", config->rmarray[rmidx]);
		fpbcon = fopen(filename, "r");
		/* posidx always starts from 1 for each file */
		ret = 0;
		posidx = 1;
		while(ret != -1){
			ret = rssifgpt_update_file(currentfgpt, fpbcon, config->rmarray[rmidx], posidx);
			if (ret == 0){
				posidx++;
				/* new tail */
				rssimap->fgpt_tail = currentfgpt; 
				if (currentfgpt->next){
					/* new tail is created in rssifgpt_update_file(), this should be always true */
					currentfgpt = currentfgpt->next;
				}
			}
		}
		/* ret == -1, reach the end of file */
		/* move to next room */
		fclose(fpbcon);
	}

	return 0;

}


/** 
  * rssimap_load_cache - a wrapper to initialize the local cached map
  * 					update can be from local map files or server database
  * 					prior to any update to this cache map all beacon fingerprints MUST BE flushed first
  * @struct rssi_map *rssimap - a pointer to a rssi fingerprint map
  * returns: 0 on success; -1 on failure
  */
int rssimap_load_cache(struct rssi_map *rssimap)
{
	/* TODO */
	int i;
	struct configparam *config = rssimap->globalconfig;
	
	printf("map filenames are ");
	for (i = 0; i < config->num_rooms; i++){
                printf("sta_%d, ", config->rmarray[i]);
        }
        printf("\n");

	if (rssimap->fgpt_tail->next->next){
		/* flush all existing beacon fingerprints */
		rssifgpt_flush_bcon(rssimap->fgpt_tail);
	}
	return rssimap_load_cache_file(rssimap);
}

/** 
  * rssimap_load_local_file - update loacl fingerprint from log file
  * @struct rssi_map *rssimap - a pointer to a rssi fingerprint map
  * @int localroom - room number, to specify filename
  * @int localpos - location marker, to specify fingerprint record
  * returns: 0 on success; -1 on failure
  */
int rssimap_load_local_file(struct rssi_map *rssimap, int localroom, int localpos)
{
	struct rssi_fgpt *localfgpt = rssimap->fgpt_head;
	int ret;
	FILE *fplocal;
	char filename[20];

	/* open log file */
	sprintf(filename, "stat_%d", localroom);
	fplocal = fopen(filename, "r");

	/* add local fingerprint */
	ret = rssifgpt_update_file(localfgpt, fplocal, localroom, localpos);

	fclose(fplocal);

	return ret;

}

